// Copyright (c) [2019] [huazhonghua]
// [理解] is licensed under the Mulan PSL v1.
// You can use this software according to the terms and conditions of the Mulan PSL v1.
// You may obtain a copy of Mulan PSL v1 at:
//     http://license.coscl.org.cn/MulanPSL
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
// PURPOSE.
// See the Mulan PSL v1 for more details.

#ifndef DATA_SERVER_H
#define DATA_SERVER_H

#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>
#include <pthread.h>
#include <wchar.h>

#include "structure.h"
#include "worker.h"

#define DATA_PORT 8080
#define HEADER_BUFFER_SIZE 2048
#define GEN_BUFFER_SIZE 200000
#define METHOD_SIZE 10
#define PROTOCOL_SIZE 20

void cleanup_data_worker_thread(Node* worker_data) {
	Node* thread_id = get_linked(worker_data, THREAD_ID);
	free(thread_id->other.pointer);
	free_node(thread_id);
	free_node(worker_data);
}

int copy_until(char* buffer, size_t buffer_size, char* text, char delimitor) {
	int i;
	for(i = 0; text[i] != delimitor; ++i) {
		if(text[i] == '\0' || i == buffer_size) return -1;
		buffer[i] = text[i];
	}

	buffer[i] = '\0';
	return i;
}

struct LinkTableGenData {
	wchar_t* buffer;
	int buffer_left;
	int i;
	Node* all;
	int status;
};

typedef struct LinkTableGenData LinkTableGenData;

int update_check_link_gen(LinkTableGenData* gen_data, int count) {
	if(count < 0) {
		gen_data->status = 0;
		return 0;
	}

	gen_data->buffer += count;
	gen_data->buffer_left -= count;
	return 1;
}

int swprint_update(wchar_t** buffer_p, int* buffer_left_p, wchar_t* str) {
	int written = swprintf(*buffer_p, *buffer_left_p, str);
	if(written < 0) return written;
	(*buffer_p) += written;
	(*buffer_left_p) -= written;
	return written;
}

int print_node_for_link_entry(LinkTableGenData* gen_data, Node* node) {
	if(node == NULL) {
		return swprintf(gen_data->buffer, gen_data->buffer_left, L"NULL");
	}
	else if(node == gen_data->all) {
		return swprintf(gen_data->buffer, gen_data->buffer_left, L"all");
	}

	int is_string = is_instance_of(node, STRING);

	Node* string = is_string ? node : get_linked(node, STRING);

// print type if null
	if(string != NULL) {
		List* list = string->other.pointer;

		wchar_t* buffer = gen_data->buffer;
		int buffer_left = gen_data->buffer_left;
		int written;
		int i;

		if(is_string) {
			written = swprint_update(&buffer, &buffer_left, L"\"");
			if(written < 0) return written;
		}

		for(i = 0; i < list->length && buffer_left > 1; ++i) {
			wchar_t c = list->nodes[i]->part;
			switch(c) {
				case L'<':
					written = swprint_update(&buffer, &buffer_left, L"&lt;");
					if(written < 0) return written;
					break;
				case L'>':
					written = swprint_update(&buffer, &buffer_left, L"&gt;");
					if(written < 0) return written;
					break;
				case L'&':
					written = swprint_update(&buffer, &buffer_left, L"&amp;");
					if(written < 0) return written;
					break;
				default:
					*buffer = c;
					++buffer;
					--buffer_left;
			}
		}

		if(is_string) {
			written = swprint_update(&buffer, &buffer_left, L"\"");
			if(written < 0) return written;
		}

		*buffer = '\0';
		
		return i == list->length ? gen_data->buffer_left - buffer_left : -1;
	}
	return swprintf(gen_data->buffer, gen_data->buffer_left, L"%d", node->part);
}

int generate_linked_entry(void* state, Link* link) {
	LinkTableGenData* gen_data = state;
	Node* all = gen_data->all;
	Node* link_node = get_linked(all, link->part);
	Node* linked_node = link->target;

	Node* linked_type = get_linked(linked_node, TYPE);

	int count = swprintf(gen_data->buffer, gen_data->buffer_left, L"<tr><td><a href=\"%d/\">%d</a></td><td>", link->part, link->part);
	if(!update_check_link_gen(gen_data, count)) return 0;

	count = print_node_for_link_entry(gen_data, link_node);
	if(!update_check_link_gen(gen_data, count)) return 0;

	if(linked_node == NULL) {
		count = swprintf(gen_data->buffer, gen_data->buffer_left, L"</td><td></td><td>");
		if(!update_check_link_gen(gen_data, count)) return 0;
	}
	else {
		count = swprintf(gen_data->buffer, gen_data->buffer_left, L"</td><td>%d</td><td>", linked_node->part);
		if(!update_check_link_gen(gen_data, count)) return 0;
	}

	if(!is_tier_1(linked_type, INSTANCE)) {
		count = print_node_for_link_entry(gen_data, linked_node);
		if(!update_check_link_gen(gen_data, count)) return 0;
	}

	count = swprintf(gen_data->buffer, gen_data->buffer_left, L"</td></tr>\n");
	if(!update_check_link_gen(gen_data, count)) return 0;

	return 1;
}

int print_list_table(LinkTableGenData* gen_data, Node* node) {
	int length = get_list_node_length(node);
	for(int i = 0; i < length; ++i) {
		Node* item_node = get_list_node_item(node, i);
		Node* type = get_linked(item_node, TYPE);
		Node* free = get_linked(item_node, FREE);

		int count = swprintf(gen_data->buffer, gen_data->buffer_left, L"<tr><td><a href=\"o.%d/\">%d</a></td><td>", i, i);
		if(!update_check_link_gen(gen_data, count)) return 0;

		count = print_node_for_link_entry(gen_data, item_node);
		if(!update_check_link_gen(gen_data, count)) return 0;

		count = swprintf(gen_data->buffer, gen_data->buffer_left, L"</td><td>\n");
		if(!update_check_link_gen(gen_data, count)) return 0;

		count = print_node_for_link_entry(gen_data, type);
		if(!update_check_link_gen(gen_data, count)) return 0;

		count = swprintf(gen_data->buffer, gen_data->buffer_left, L"</td><td>%d</td></tr>\n", free == NULL ? 0 : free->part);
		if(!update_check_link_gen(gen_data, count)) return 0;
	}
	return 1;
}

int select_node_by_path(Node** node_p, char* path) {
	char* c = path;
	char* end;
	while(*c != '\0') {
		if(*c != '/') return 0;
		++c;
		if(*c == '\0') break;

		int get_array_item = 0;
		if(*c == 'o' && *(c + 1) == '.') {
			get_array_item = 1;
			c += 2;
		}
		long num = strtol(c, &end, 10);
		if(c == end) return 0;

		c = end;

		int i = num;
		if(get_array_item) {
			*node_p = get_list_node_item(*node_p, i);
		}
		else {
			*node_p = get_linked(*node_p, i);
		}
		if(*node_p == NULL) return 0;
	}
	return 1;
}

void send_404(Node* socket) {
	char buffer[120];
	char* body = "not found";
	sprintf(buffer, "HTTP/1.1 404 Not Found\r\nContent-Type: text/plain\r\nContent-Length: %ld\r\n\r\n%s", strlen(body), body);
	send(socket->part, buffer, strlen(buffer), 0);
}

void send_buffer_full(Node* socket) {
	char buffer[120];
	char* body = "buffer full";
	sprintf(buffer, "HTTP/1.1 500 Internal Server Error\r\nContent-Type: text/plain\r\nContent-Length: %ld\r\n\r\n%s", strlen(body), body);
	send(socket->part, buffer, strlen(buffer), 0);
}

void send_400(Node* socket) {
	char buffer[120];
	char* body = "bad request";
	sprintf(buffer, "HTTP/1.1 400 Bad Request\r\nContent-Type: text/plain\r\nContent-Length: %ld\r\n\r\n%s", strlen(body), body);
	send(socket->part, buffer, strlen(buffer), 0);
}

void generate_body(LinkTableGenData* gen_data, Node* node) {
	int count = swprintf(gen_data->buffer, gen_data->buffer_left,
		L"<!DOCTYPE html>\n<html>\n<head><style>body { font-family: Arial; }</style></head>\n<body>\n<table>\n<tr><td>Part</td><td>Node</td>\n</tr>"
		L"<tr><td>%d</td><td>", node->part);

	if(!update_check_link_gen(gen_data, count)) return;

	count = print_node_for_link_entry(gen_data, node);
	if(!update_check_link_gen(gen_data, count)) return;

	count = swprintf(gen_data->buffer, gen_data->buffer_left,
		L"</td></tr>\n</table>\n<div style=\"margin-top: 20px\"><a href=\"..\">..</a></div>\n<table>\n<tr><td>Link</td><td style=\"min-width: 250px\"></td>"
		L"<td>Node</td><td></td></tr>\n");

	if(!update_check_link_gen(gen_data, count)) return;

	iterate_links(node, generate_linked_entry, gen_data);
	if(gen_data->status == 0) return;

	if(is_instance_of(node, LIST)) {
		count = swprintf(gen_data->buffer, gen_data->buffer_left, L"</table>\n<table style=\"margin-top: 20px\">\n<tr><td>List</td><td></td><td></td></tr>\n");
		if(!update_check_link_gen(gen_data, count)) return;
		if(!print_list_table(gen_data, node)) return;
	}

	count = swprintf(gen_data->buffer, gen_data->buffer_left, L"</table>\n</body>\n</html>");
	if(!update_check_link_gen(gen_data, count)) return;
}

void* run_data_server_worker(void* args) {
	Node* worker_data = args;
	Node* data_server = get_linked(worker_data, DATA_SERVER);
	Node* all = get_linked(data_server, ALL);

	char header_buffer[HEADER_BUFFER_SIZE] = {0};
	char method[METHOD_SIZE];
	char path[HEADER_BUFFER_SIZE];
	char protocol[PROTOCOL_SIZE];
	char line_buffer[HEADER_BUFFER_SIZE] = {0};
	char* c;

	Node* socket = get_linked(worker_data, SOCKET);

	FILE* fd = fdopen(socket->part, "rw");

	if(fd != NULL) {

		while(1) {
			c = fgets(header_buffer, HEADER_BUFFER_SIZE, fd);
		 	if(c == NULL) break;

			int i = copy_until(method, METHOD_SIZE, c, ' ');
			if(i < 0) {
				send_400(socket);
				break;
			}
			c += (i + 1);

			i = copy_until(path, HEADER_BUFFER_SIZE, c, ' ');
			if(i < 0) {
				send_400(socket);
				break;
			}
			c += (i + 1);

			i = copy_until(protocol, HEADER_BUFFER_SIZE, c, '\r');
			if(i < 0) {
				send_400(socket);
				break;
			}
			c += (i + 1);

			if(*c != '\n') {
				send_400(socket);
				break;
			}

			do {
				c = fgets(header_buffer, HEADER_BUFFER_SIZE, fd);
			} while(c != NULL && strcmp(c, "\r\n") != 0);

			if(c == NULL) break;

			int keep_alive = 1;

			Node* node = all;
			if(!select_node_by_path(&node, path)) {
				send_404(socket);
				if(keep_alive) continue;
				else break;
			}

			wchar_t resp_buffer[GEN_BUFFER_SIZE];

			LinkTableGenData gen_data;
			gen_data.i = 0;
			gen_data.buffer_left = GEN_BUFFER_SIZE;
			gen_data.buffer = resp_buffer;
			gen_data.all = all;
			gen_data.status = 1;

			generate_body(&gen_data, node);

			if(gen_data.status == 0) {
				send_buffer_full(socket);
				if(keep_alive) continue;
				else break;
			}

			int mb_buffer_size = GEN_BUFFER_SIZE * 3;
			char mb_buffer[mb_buffer_size];
			int body_length = wcstombs(mb_buffer, resp_buffer, mb_buffer_size);

			int header_buffer_size = 300;
			char header_buffer[header_buffer_size];
			int count = snprintf(header_buffer, header_buffer_size, "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\nContent-Length: %d\r\n\r\n",
				body_length);

			if(count < 0) {
				send_buffer_full(socket);
				if(keep_alive) continue;
				else break;
			}

			send(socket->part, header_buffer, count, 0);
			send(socket->part, mb_buffer, body_length, 0);
		}

		fclose(fd);
	}

	Node* workers = get_linked(data_server, WORKER);
	pthread_mutex_t* mutex = get_linked(data_server, LOCK)->other.pointer;

	pthread_mutex_lock(mutex);

	unlink_node(workers, worker_data->part);

	Node* skip_cleaning = get_linked(worker_data, SKIP_CLEANING);

	if(skip_cleaning == NULL) {
		cleanup_data_worker_thread(worker_data);
	}

	pthread_mutex_unlock(mutex);

	close(socket->part);
	free_node(socket);

	return NULL;
}

void start_thread(Node* all, Node* thread_data, void* (*run)(void*)) {
	Node* id_node = create_node(all);
	id_node->other.pointer = malloc(sizeof(pthread_t));
	link_node(thread_data, THREAD_ID, id_node);
	pthread_create(id_node->other.pointer, NULL, run, thread_data);
}

void free_thread(Node* thread_data) {
	Node* id_node = get_linked(thread_data, THREAD_ID);
	unlink_node(thread_data, THREAD_ID);
	stop_thread_by_id(id_node);
}

int shutdown_data_worker_socket(void* state, Node* worker) {
	Node* all = state;
	link_node(worker, SKIP_CLEANING, get_int(all, 1));
	Node* socket = get_linked(worker, SOCKET);
	shutdown(socket->part, SHUT_RDWR);
	return 1;
}

int wait_cleanup_data_worker(void* state, Node* worker) {
	free_thread(worker);
	free_node(worker);
	return 1;
}

void* run_data_server(void* args) {
	Node* data_server = args;
	Node* status = get_linked(data_server, STATUS);

	int listen_socket, new_socket, read_num;
	struct sockaddr_in address;
	int opt = 1;
	int addr_len = sizeof(address);

	if((listen_socket = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
		perror("socket failed");
		return NULL;
	}

	if(setsockopt(listen_socket, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
		perror("setsockopt failed");
		return NULL;
	}

	address.sin_family = AF_INET;
	address.sin_addr.s_addr = INADDR_ANY;
	address.sin_port = htons(DATA_PORT);
	if(bind(listen_socket, (struct sockaddr *)&address, sizeof(address)) < 0) {
		perror("bind failed");
		return NULL;
	}

	if(listen(listen_socket, 3) < 0) {
		perror("listen error");
		return NULL;
	}

	Node* all = get_linked(data_server, ALL);
	int worker_num = 0;
	Node* workers = create_node(all);
	link_node(data_server, WORKER, workers);

	create_lock(all, data_server);
	pthread_mutex_t* mutex = get_linked(data_server, LOCK)->other.pointer;

	while(status->part == ACTIVE) {
		if((new_socket = accept(listen_socket, (struct sockaddr *)&address, (socklen_t*)&addr_len)) < 0) {
			perror("accept error");
			return NULL;
		}

		Node* worker_data = create_node(all);

		link_node(worker_data, DATA_SERVER, data_server);

		pthread_mutex_lock(mutex);
		// should set a limit
		worker_data->part = worker_num++;
		link_node(workers, worker_data->part, worker_data);

		pthread_mutex_unlock(mutex);

		Node* socket = create_node(all);
		link_node(worker_data, SOCKET, socket);
		socket->part = new_socket;

		start_thread(all, worker_data, run_data_server_worker);
	}

	close(listen_socket);

	pthread_mutex_lock(mutex);

	iterate_linked(workers, shutdown_data_worker_socket, all);

	pthread_mutex_unlock(mutex);

	iterate_linked(workers, wait_cleanup_data_worker, NULL);

	delete_lock(data_server);

	free_node(workers);

	free_node(data_server);

	return NULL;
}

void connect_to_data_server() {
	int sock = 0;
	struct sockaddr_in serv_addr;

	if((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) return;

	serv_addr.sin_family = AF_INET;
	serv_addr.sin_port = htons(DATA_PORT);

	if(inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) return;

	if(connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) return;
}

void start_data_server(Node* all) {
	Node* data_server = create_node(all);
	link_node(all, DATA_SERVER, data_server);
	link_node(data_server, ALL, all);

	Node* status = create_node(all);
	status->part = ACTIVE;
	link_node(data_server, STATUS, status);

	start_thread(all, data_server, run_data_server);
}

void stop_data_server(Node* all) {
	Node* data_server = get_linked(all, DATA_SERVER);

	Node* status = get_linked(data_server, STATUS);
	status->part = STOPPING;

	connect_to_data_server();

	Node* id_node = get_linked(data_server, THREAD_ID);
	pthread_t* thread_id = id_node->other.pointer;
	pthread_join(*thread_id, NULL);
	free(thread_id);
	free_node(id_node);

	Node* thread_ids = get_linked(data_server, CHILD_THREAD_ID);
	free_node(thread_ids);

	free_node(status);
}

#endif
